今天寫的主題型別轉換 (Type Casting) 是在開發時會經常使用到,最常看到就是使用 as? 或 as! 的符號進行轉換,以及 is 的使用,而這些到底有什麼差別,今天就來做一次完整的介紹。
型別轉換是一種檢查實例型別的方法,或者將該實例視為與自己的類層次結構中不同位置的父類別或子類別。
Swift 中的型別轉換是使用 is 和 as 運算符實現的。這兩個運算符提供了一種簡單而富有表現力的方法來檢查值的型別或將值轉換為其他型別。
我們還可以使用型別轉換來檢查型別是否符合協定。
我們可以使用型別轉換和類和子類的層次結構來檢查特定類實例的型別,並將該實例轉換為同一層次結構中的另一個類。下面的三個代碼片段定義了類的層次結構和包含這些類的實例的數組,為型別轉換的範例。
第一個片段定義了一個名為 MediaItem
的新基礎類。此類為數字媒體庫中顯示的任何型別的項目提供基本功能。具體來說,它宣告了型別 String
的屬性 name
和名稱初始值設定項 init
。(假設所有媒體項目,包括所有電影和歌曲,都會有一個名字。)
下一個片段定義了 MediaItem
的兩個子類。第一個子類 Movie
包含有關電影或電影的其他資訊。它在基礎類 MediaItem
的前面添加了一個屬性 director
,並帶有相應的初始值設定項。第二個子類 Song
在基類之上添加了一個屬性 artist
和初始值設定項:
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
最後一段代碼建立了一個名為 library
的常數數組,其中包含兩個 Movie
實例和三個 Song
實例。通過數組文字的內容初始化來推斷庫數組的型別。 Swift 的型別檢查器能夠推斷出 Movie
和 Song
有一個共同的父類 MediaItem
,因此它為數組 library
推斷出 [MediaItem]
的型別:
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// the type of "library" is inferred to be [MediaItem]
存儲在 library
中的項目仍然是 Movie
和 Song
實例。但是,如果迭代此數組的內容,則我們收到的項目將歸類為 MediaItem
,而不是 Movie
或 Song
。要將它們作為原始型別使用,需要檢查其型別,或將它們向下轉換為其他型別,如下所述。
使用型別檢查運算符 (is) 檢查實例是否屬於某個子類型別。如果實例屬於該子類型別,則型別檢查運算符返回 true
,否則返回 false
。
下面的範例定義了兩個變數 movieCount
和 songCount
,它們計算 library
數組中 Movie
和 Song
實例的數量:
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"
此範例遍歷 library
數組中的所有項。在每次傳遞時,for-in 循環將常數 item
設置為數組中的下一個 MediaItem
。
如果當前 MediaItem
是 Movie
實例,則 item is Movie
返回true
,如果不是,則返回 false
。同樣,item is Song
檢查項目是否是 Song
實例。在 for-in 循環結束時,movieCount
和 songCount
的值包含每種型別找到多少個 MediaItem
實例。
某個類型別的常數或變數實際上可能是指子類的實例。在我們認為是這種情況的情況下,我們可以嘗試使用型別轉換運算符 (as?
或 as!
) 向下轉換為子類型別。
由於向下轉換可能會失敗,因此型別轉換運算符有兩種不同的形式。條件形式,為 ?
,返回嘗試向下轉換的型別的可選值。強制形式,為 !
,嘗試向下轉換並強制解包將結果視為單一複合動作。
當不確定向下轉換是否成功時,請使用型別轉換運算符的條件形式 (as?
)。這種形式的運算符將始終返回一個可選值,如果無法進行向下轉換,則該值將為 nil
。這使我們可以檢查成功的向下轉型。
僅當確定向下轉換將總是成功時,才使用型別轉換運算符的強制形式 (as!
)。如果嘗試向下轉換為不正確的類型別,則此形式的運算符將觸發運行時錯誤。
下面的範例遍歷 library
中的每個 MediaItem
,並為每個項印出適當的描述。要做到這一點,它需要將每個項目作為真正的 Movie
或 Song
來訪問,而不僅僅是作為 MediaItem
。這是必要的,以便它能夠訪問 Movie
或 Song
的屬性 director
或 artist
以使用在描述中。
在此範例中,數組中的每個項目可能是 Movie
,也可能是 Song
。事先不知道每個項目使用哪個實際類,因此使用型別轉換操作符的條件形式 (as?
) 來檢查每次循環中的向下轉換是合適的:
for item in library {
if let movie = item as? Movie {
print("Movie: \(movie.name), dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: \(song.name), by \(song.artist)")
}
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
該範例首先嘗試將當前項目向下轉換為 Movie
。因為 item
是一個 MediaItem
實例,所以它可能是一個 Movie
;同樣,它也可能是一首 Song
,甚至只是一個基礎 MediaItem
。由於這種不確定性,當嘗試向下轉換為子類型別時,型別轉換運算符的 as?
形式返回可選值。item as? Movie
的結果為 Movie?
型別 或 Movie
的可選型別。
當應用於陣列 library
中的 Song
實例時,向下轉換為 Movie
失敗。為了解決這個問題,上面的範例使用可選綁定來檢查 Movie
的可選型別是否實際包含一個值(即找出向下轉換是否成功。)這個可選綁定是寫成 "if let movie = item as? Movie
",可以理解為:
「嘗試將 item
作為 Movie
訪問。如果成功,請將名為 movie
的新臨時常數設置為存儲在返回 Movie
可選型別中的值。」
如果向下轉換成功,則屬性 Movie
將用於印出該 Movie
實例的描述,包括其 director
的名稱。類似的原理用於檢查 Song
實例,並在 library
中找到 Song
時印出適當的描述(包括 artist
的姓名)。
型別轉換實際上不會修改實例或改變它的值。基礎實例保持不變;它被簡單地處理和訪問,作為被轉換的類別的實例。
Swift 提供了兩種特殊型別來處理非特定型別:
Any
可以表示任何型別的實例,包括函數型別。AnyObject
可以表示任何類型別的實例。僅當明確需要它們提供的行為和功能時才使用 Any
和 AnyObject
。最好具體了解希望在代碼中使用的型別。
下面是使用 Any
處理不同型別混合的範例,包括函數型別和非類型別。該範例創建一個名為 things
的數組,它可以儲存 Any
型別的值:
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
things
數組包含兩個 Int
值,兩個 Double
值,一個 String
值,一個型別為 Double
(Double,Double) 的元組,電影 “Ghostbusters”,以及一個帶有 String
值並返回另一個 String
值的閉包表達式。
要發現常數或變數的特定型別,只要知道型別為 Any
或 AnyObject
,可以在 switch 敘述的情況下使用 is
或 as
模式。下面的範例遍歷數組 things
中的項目,並使用 switch 敘述查詢每個項目的型別。有幾個 switch 語句將它們的匹配值綁定到指定型別的常數,以使其值可以印出:
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
Any
型別表示任何型別的值,包括可選型別。如果使用可選值的型別為Any
的值,則 Swift 會發出警告。如果確實需要將可選值用為Any
值,則可以使用as
運算符將可選明確地轉換為Any
,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning